# 通过界面，合并pdf以及excel文件
# QProgressDialog进度对话框，QMessageBox弹窗提示，QLabel文字展示

from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QFileDialog, QLabel, QMessageBox, QProgressDialog, QGroupBox, QCheckBox, QToolTip, QTabWidget, QDialog, QTextEdit
from PyQt6.QtCore import Qt, pyqtSignal, QThread
from PyQt6.QtGui import QIcon
from PIL import Image
import sys, time, pandas as pd, glob, PyPDF2, os, openpyxl
from functools import partial

class MergeThread(QThread):
    progress_changed = pyqtSignal(int)
    error_occurred = pyqtSignal(str)  # 添加错误信号

    def __init__(self, folder_path, file_type):
        super().__init__()
        self.folder_path = folder_path
        self.file_type = file_type

    def run(self):
        # print(333)
        # 执行耗时操作
        if self.file_type == "pdf":
            self.run_merge_pdf(self.folder_path)
        elif self.file_type == "excel":
            self.run_merge_excel(self.folder_path)
        elif self.file_type == "imageToPdf":
            self.run_merge_image_to_pdf(self.folder_path)

        # self.finished.emit()
    
    def getCurrentTime(self):
        # 获取当前时间戳
        current_timestamp = time.time()

        # 将时间戳格式化为可读的字符串
        time_str = time.strftime('%Y%m%d%H%M%S', time.localtime(current_timestamp))
        return time_str

    # 发送信号给进度框
    def run_send_signl(self, index, total):
        progress = int((index + 1) * 100 / total)
        self.progress_changed.emit(progress)

    # 实现同一路径下的多个pdf合并
    def run_merge_pdf(self, file_paths):
        # 提取目录路径
        folder_path = os.path.dirname(file_paths[0])
        # 创建一个PdfWriter对象，用于写入合并后的PDF
        pdf_writer = PyPDF2.PdfWriter()

        total = len(file_paths)
        # 循环遍历每个PDF文件，并将其添加到PdfWriter对象中
        for i, pdf_path in enumerate(file_paths):
            with open(pdf_path, "rb") as pdf:
                pdf_reader = PyPDF2.PdfReader(pdf)
                for page_num in range(len(pdf_reader.pages)):
                    # 逐页添加到PdfWriter对象
                    page = pdf_reader.pages[page_num]
                    pdf_writer.add_page(page)

            self.run_send_signl(i, total)        
            
        # 将合并后的PDF写入到输出文件中
        with open(folder_path + "/合并后的文件" + self.getCurrentTime() + ".pdf", "wb") as output_pdf:
            pdf_writer.write(output_pdf)

    # 实现同一路径下的多个excel文件合并
    def run_merge_excel(self, file_paths):
        # 提取目录路径
        folder_path = os.path.dirname(file_paths[0])

        # 创建一个空的DataFrame来存储合并后的数据
        merged_data = pd.DataFrame()

        # 循环读取每个Excel文件并合并到DataFrame中
        for file in file_paths:
            df = pd.read_excel(file)
            merged_data = pd.concat([merged_data, df], ignore_index=True)

        # 将合并后的数据写入新的Excel文件
        merged_data.to_excel(folder_path + "/合并后的文件" + self.getCurrentTime() + ".xlsx", index=False)

    # 实现同一路径下的多张图片合并为pdf，但有数量限制
    def run_merge_image_to_pdf(self, file_paths):
        # 提取目录路径
        folder_path = os.path.dirname(file_paths[0])
        # 确保有图片文件
        if not file_paths:
            print("文件夹中没有找到图片文件。")
            return
        
        height = 0
        heightY = 0

        for image_path in file_paths:
            # 打开图片
            image = Image.open(image_path)
            height = height + image.size[1]

        pdf = Image.new('RGB', (image.size[0], height), color='white')

        # 遍历所有图片路径
        for index, image_path in enumerate(file_paths):
            try:
                # 打开图片
                image = Image.open(image_path)

                # 将图片转换为 RGB 模式
                image = image.convert('RGB')

                # 打印图片大小信息
                # print(f"图片大小：{image.size}")
                # height = height + image.size[1]
                
                # 添加图片到PDF中
                pdf.paste(image, (0, heightY))
                heightY = heightY + image.size[1]
            except IOError:
                print(f"无法打开图片文件：{image_path}")
        
        # 保存PDF文件
        try:
            pdf.save(folder_path + "/合并后的文件" + self.getCurrentTime() + ".pdf", 'PDF')
        except Exception as e:
            print(f"保存PDF文件时出错: {e}")
            # 使用信号发送错误消息到主线程
            self.error_occurred.emit('选择的图片太多，无法合并')

class ConvertThread(QThread):
    progress_changed = pyqtSignal(int)
    error_occurred = pyqtSignal(str)  # 添加错误信号

    def __init__(self, file_paths):
        super().__init__()
        self.file_paths = file_paths

    def run(self):
        try:
            self.run_convert_images_to_pdf(self.file_paths)
        except Exception as e:
            print(f"转换图片时出错: {e}")
            self.error_occurred.emit(f"转换失败: {str(e)}")
    
    def getCurrentTime(self):
        # 获取当前时间戳
        current_timestamp = time.time()
        # 将时间戳格式化为可读的字符串
        time_str = time.strftime('%Y%m%d%H%M%S', time.localtime(current_timestamp))
        return time_str

    # 发送信号给进度框
    def run_send_signl(self, index, total):
        progress = int((index + 1) * 100 / total)
        self.progress_changed.emit(progress)

    # 实现将图片转换为PDF（每张图片一个PDF文件）
    def run_convert_images_to_pdf(self, file_paths):
        total = len(file_paths)
        if not file_paths:
            print("没有找到图片文件。")
            return
        
        # 提取目录路径
        output_folder = os.path.dirname(file_paths[0])
        
        # 遍历所有图片路径
        for i, image_path in enumerate(file_paths):
            try:
                # 打开图片
                image = Image.open(image_path)
                # 将图片转换为 RGB 模式
                image = image.convert('RGB')
                
                # 生成输出文件名（以原文件名为基础）
                base_name = os.path.basename(image_path)
                file_name = os.path.splitext(base_name)[0]
                output_path = f"{output_folder}/{file_name}.pdf"
                
                # 保存为PDF
                image.save(output_path, 'PDF')
                
                # 更新进度
                self.run_send_signl(i, total)
                
            except Exception as e:
                print(f"处理图片 {image_path} 时出错: {e}")
                self.error_occurred.emit(f"处理图片时出错: {str(e)}")
                return
        
        print("所有图片已转换为PDF文件。")

class PdfToImageThread(QThread):
    progress_changed = pyqtSignal(int)
    error_occurred = pyqtSignal(str)

    def __init__(self, file_paths):
        super().__init__()
        self.file_paths = file_paths

    def run(self):
        try:
            self.run_convert_pdf_to_images(self.file_paths)
        except Exception as e:
            print(f"转换PDF时出错: {e}")
            self.error_occurred.emit(f"转换失败: {str(e)}")

    def getCurrentTime(self):
        current_timestamp = time.time()
        time_str = time.strftime('%Y%m%d%H%M%S', time.localtime(current_timestamp))
        return time_str

    def run_send_signl(self, index, total):
        progress = int((index + 1) * 100 / total)
        self.progress_changed.emit(progress)

    def run_convert_pdf_to_images(self, file_paths):
        total = len(file_paths)
        if not file_paths:
            print("没有找到PDF文件。")
            return
        
        # 提取目录路径
        output_folder = os.path.dirname(file_paths[0])
        
        # 遍历所有PDF文件
        for i, pdf_path in enumerate(file_paths):
            try:
                # 打开PDF文件
                import fitz  # PyMuPDF
                pdf_document = fitz.open(pdf_path)
                
                # 获取PDF文件名（不含扩展名）
                base_name = os.path.basename(pdf_path)
                file_name = os.path.splitext(base_name)[0]
                
                # 遍历PDF的每一页
                for page_num in range(len(pdf_document)):
                    # 获取页面
                    page = pdf_document[page_num]
                    
                    # 将页面转换为图片
                    pix = page.get_pixmap(matrix=fitz.Matrix(2, 2))  # 2x缩放以获得更好的质量
                    
                    # 保存图片
                    output_path = f"{output_folder}/{file_name}_page{page_num+1}.png"
                    pix.save(output_path)
                
                # 关闭PDF文件
                pdf_document.close()
                
                # 更新进度
                self.run_send_signl(i, total)
                
            except Exception as e:
                print(f"处理PDF {pdf_path} 时出错: {e}")
                self.error_occurred.emit(f"处理PDF时出错: {str(e)}")
                return
        
        print("所有PDF已转换为图片文件。")

class SplitThread(QThread):
    progress_changed = pyqtSignal(int)
    error_occurred = pyqtSignal(str)  # 添加错误信号

    def __init__(self, file_path):
        super().__init__()
        self.file_path = file_path

    def run(self):
        try:
            self.split_excel(self.file_path)
        except Exception as e:
            print(f"拆分Excel时出错: {e}")
            self.error_occurred.emit(f"拆分失败: {str(e)}")

    def split_excel(self, file_path):
        try:
            wb = openpyxl.load_workbook(file_path)
            for sheet_name in wb.sheetnames:
                sheet = wb[sheet_name]
                new_wb = openpyxl.Workbook()
                new_sheet = new_wb.active
                new_sheet.title = sheet.title
                for row in sheet.iter_rows():
                    new_sheet.append([cell.value for cell in row])
                new_file_path = file_path.replace('.xlsx', f'_{sheet_name}.xlsx')
                new_wb.save(new_file_path)
            wb.close()
            print("Excel sheets are split successfully.")
        except Exception as e:
            print(f"Error occurred: {e}")
            self.error_occurred.emit(f"拆分Excel时出错: {str(e)}")
            raise

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(600, 400)

        # 设置客户端标题
        self.setWindowTitle('Tool_v2.0.0')
         # 设置窗口图标
        self.setWindowIcon(QIcon(r'D:\Programming\Coding\27.QT5工具端\tool.png'))

        # 调用设置界面方法
        self.setUI()

        # 设置全局变量
        self.get_file_method = "file"
        self.convert_file_method = "file"

    # 客户端的页面布局
    def setUI(self):
        # 创建主窗口布局,垂直布局管理器
        layout = QVBoxLayout()
        
        # 创建Tab栏
        tab_widget = QTabWidget()
        
        # 创建功能Tab
        function_tab = QWidget()
        function_layout = QVBoxLayout()
        
        # 创建一个水平布局
        BoxHLayout = QHBoxLayout()
        
        # 创建一个 QGroupBox 实例，用于放置合并操作
        self.MergeGroupBox = QGroupBox('合并操作')
        
        # 把合并组框放入水平布局
        BoxHLayout.addWidget(self.MergeGroupBox)
        
        # 创建一个垂直布局，用于排列合并组框中的按钮
        merge_button_layout = QVBoxLayout()
        
        # 创建一个按钮合并pdf
        self.mergePdfButton = QPushButton("合并Pdf文件")
        self.mergePdfButton.clicked.connect(partial(self.choose_folder_and_run, "pdf"))
        
        # 创建一个按钮合并excel
        self.mergeExcelButton = QPushButton("合并Excel文件")
        self.mergeExcelButton.clicked.connect(partial(self.choose_folder_and_run, "excel"))
        
        # 创建一个按钮合并图片为pdf
        self.mergeImageToPdfButton = QPushButton("合并图片为Pdf文件")
        self.mergeImageToPdfButton.clicked.connect(partial(self.choose_folder_and_run, "imageToPdf"))
        
        # 把按钮放进布局
        merge_button_layout.addWidget(self.mergePdfButton)
        merge_button_layout.addWidget(self.mergeExcelButton)
        merge_button_layout.addWidget(self.mergeImageToPdfButton)
        
        # 将布局放入组框
        self.MergeGroupBox.setLayout(merge_button_layout)
        
        # 创建一个 QGroupBox 实例，用于放置拆分操作
        self.SplitGroupBox = QGroupBox('拆分操作')
        
        # 创建一个垂直布局，用于排列拆分组框中的按钮
        split_button_layuot = QVBoxLayout()
        
        # 创建按钮
        self.splitExcelButton = QPushButton("拆分Excel文件")
        self.splitExcelButton.clicked.connect(partial(self.choose_file_and_run, "excel"))
        
        # 把按钮放进垂直布局
        split_button_layuot.addWidget(self.splitExcelButton)
        
        # 把布局放进组框
        self.SplitGroupBox.setLayout(split_button_layuot)
        
        # 把拆分组框放入横向布局
        BoxHLayout.addWidget(self.SplitGroupBox)
        
        # 创建一个 QGroupBox 实例，用于放置转换操作
        self.ConvertGroupBox = QGroupBox('转换操作')
        
        # 创建一个垂直布局，用于排列转换组框中的按钮
        convert_button_layout = QVBoxLayout()
        
        # 创建按钮用于将图片转换为PDF
        self.convertImagesToPdfButton = QPushButton("图片转换为PDF")
        self.convertImagesToPdfButton.clicked.connect(self.convert_images_to_pdf)
        
        # 创建按钮用于将PDF转换为图片
        self.convertPdfToImagesButton = QPushButton("PDF转换为图片")
        self.convertPdfToImagesButton.clicked.connect(self.convert_pdf_to_images)
        
        # 把按钮放进垂直布局
        convert_button_layout.addWidget(self.convertImagesToPdfButton)
        convert_button_layout.addWidget(self.convertPdfToImagesButton)
        
        # 把布局放进组框
        self.ConvertGroupBox.setLayout(convert_button_layout)
        
        # 把转换组框放入横向布局
        BoxHLayout.addWidget(self.ConvertGroupBox)
        
        # 把横向布局添加到功能Tab的布局中
        function_layout.addLayout(BoxHLayout)
        
        # 创建一个复选框并设置文本
        self.checkbox = QCheckBox("以文件选择")
        self.checkbox.setChecked(True)  # 设置默认选中
        self.checkbox.stateChanged.connect(self.checkbox_state_changed)
        
        # 将复选框添加到底部
        function_layout.addWidget(self.checkbox)
        
        function_tab.setLayout(function_layout)
        
        # 创建版本Tab
        version_tab = QWidget()
        version_layout = QVBoxLayout()
        
        # 创建文本编辑器
        version_text = QTextEdit()
        version_text.setReadOnly(True)  # 设置为只读
        
        # 设置版本历史内容
        version_history = """版本历史：

v1.0.0 (2024-03-26)
- 初始版本发布
- 实现PDF文件合并功能
- 实现Excel文件合并功能
- 实现图片合并为PDF功能
- 实现Excel文件拆分功能

v1.1.0 (2025-03-26)
- 实现图片转换为PDF功能

v2.0.0 (2025-05-25)
- 实现PDF文件转换为图片功能
- 更改页面布局，新增TAB栏，版本、功能介绍"""
        
        version_text.setText(version_history)
        version_layout.addWidget(version_text)
        version_tab.setLayout(version_layout)
        
        # 创建介绍Tab
        intro_tab = QWidget()
        intro_layout = QVBoxLayout()
        
        # 创建文本编辑器
        intro_text = QTextEdit()
        intro_text.setReadOnly(True)  # 设置为只读
        
        # 设置功能介绍内容
        intro_content = """功能介绍：
        
1. 合并操作
   - 合并PDF文件：将多个PDF文件合并为一个
   - 合并Excel文件：将多个Excel文件合并为一个
   - 合并图片为PDF：将多张图片合并为一个PDF文件

2. 拆分操作
   - 拆分Excel文件：将Excel文件按工作表拆分为多个文件

3. 转换操作
   - 图片转换为PDF：将图片文件转换为PDF格式
   - PDF转换为图片：将PDF文件转换为图片格式

4. 文件选择方式
   - 支持以文件方式选择：可以自由选择多个文件
   - 支持以文件夹方式选择：自动选择文件夹中的所有相关文件
   
By XieTian&AI"""
        
        intro_text.setText(intro_content)
        intro_layout.addWidget(intro_text)
        intro_tab.setLayout(intro_layout)
        
        # 添加Tab页
        tab_widget.addTab(function_tab, "功能")
        tab_widget.addTab(version_tab, "版本")
        tab_widget.addTab(intro_tab, "介绍")
        
        # 将Tab栏添加到主布局
        layout.addWidget(tab_widget)
        
        # 创建主窗口
        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)

    # 弹窗提示
    def getMessageBox(self, message):
        QMessageBox.information(self, '提示', message)
    
    # 复选框状态变化时调用的方法
    def checkbox_state_changed(self, state):
        if state == 2:  # Qt.Checked
            self.checkbox.setText("以文件选择")
            self.get_file_method = "file"
            self.convert_file_method = "file"
        else:
            self.checkbox.setText("以文件夹选择")
            self.get_file_method = "folder"
            self.convert_file_method = "folder"

    # 选择指定文件夹的所有文件或文件夹中的多个文件
    def choose_folder_and_run(self, file_type):
        file_paths = []
        folder_path = None  # 初始化folder_path变量
        try:
            if self.get_file_method == "file":
                if file_type == "pdf":
                    file_paths, _ = QFileDialog.getOpenFileNames(self, "选择本地文件", "", "PDF Files (*.pdf)")
                elif file_type == "excel":
                    file_paths, _ = QFileDialog.getOpenFileNames(self, "选择本地文件", "", "Excel Files (*.xlsx *.xls)")
                elif file_type == "imageToPdf":
                    file_paths, _ = QFileDialog.getOpenFileNames(self, "选择本地文件", "", "Image Files (*.png *.jpg *.jpeg *.bmp *.gif)")
            elif self.get_file_method == "folder":
                folder_path = QFileDialog.getExistingDirectory(self, "选择本地文件夹")
                if folder_path:  # 确保用户选择了文件夹
                    if file_type == "pdf":
                        file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith(".pdf")]
                    elif file_type == "excel":
                        file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith(".xlsx")]
                    elif file_type == "imageToPdf":
                        file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
            
            # 如果用户选择了文件且文件列表不为空，则启动处理线程
            if file_paths:
                self.start_thread(file_paths, file_type, "merge")
            elif folder_path and not file_paths:
                # 如果选择了文件夹但没有找到符合条件的文件
                self.getMessageBox(f"所选文件夹中没有找到{file_type}类型的文件")
        except Exception as e:
            # 捕获任何可能发生的异常
            print(f"选择文件过程中出错: {e}")
            self.getMessageBox(f"操作失败: {str(e)}")

    # 选择指定文件夹中的单个文件
    def choose_file_and_run(self, file_type):
        try:
            file_path, _ = QFileDialog.getOpenFileName(self, "选择本地文件", "", "Excel Files (*.xlsx *.xls)")
            # 确保用户选择了文件
            if file_path:
                self.start_thread(file_path, "excel", "split")
        except Exception as e:
            print(f"选择文件过程中出错: {e}")
            self.getMessageBox(f"操作失败: {str(e)}")

    def start_thread(self, file_paths, file_type, handle):
        try:
            if handle == "merge":
                self.getProgressDialog("合并进度")
                # 创建并启动子线程
                thread = MergeThread(file_paths, file_type)
                thread.progress_changed.connect(self.update_progress)
                thread.finished.connect(self.merge_finished)
                thread.error_occurred.connect(self.handle_error)
                thread.start()

                self.progress_dialog.exec()
            elif handle == "split":
                # 创建并启动子线程
                self.splitThread = SplitThread(file_paths)
                self.splitThread.finished.connect(self.split_fineshed)
                self.splitThread.error_occurred.connect(self.handle_error)
                self.splitThread.start()
        except Exception as e:
            print(f"启动线程过程中出错: {e}")
            self.getMessageBox(f"操作失败: {str(e)}")
            # 确保进度对话框被关闭
            if hasattr(self, 'progress_dialog') and self.progress_dialog.isVisible():
                self.progress_dialog.close()

    # 进度对话框
    def getProgressDialog(self, title="合并进度"):
        # 创建进度对话框
        self.progress_dialog = QProgressDialog(title, "取消", 0, 100, self)
        self.progress_dialog.setWindowModality(Qt.WindowModality.WindowModal)
        

    def update_progress(self, progress):
        # 为进度对话框设置进度百分比
        self.progress_dialog.setValue(progress)

    def merge_finished(self):
        # 关闭进度对话框
        self.progress_dialog.close()
        self.getMessageBox("合并完成")

    def split_fineshed(self):
        self.getMessageBox("拆分完成")

    def handle_error(self, message):
        # 确保进度对话框被关闭
        if hasattr(self, 'progress_dialog') and self.progress_dialog.isVisible():
            self.progress_dialog.close()
        self.getMessageBox(message)

    # 选择图片并转换为PDF
    def convert_images_to_pdf(self):
        file_paths = []
        folder_path = None  # 初始化folder_path变量
        try:
            if self.convert_file_method == "file":
                file_paths, _ = QFileDialog.getOpenFileNames(self, "选择图片文件", "", "Image Files (*.png *.jpg *.jpeg *.bmp *.gif)")
            elif self.convert_file_method == "folder":
                folder_path = QFileDialog.getExistingDirectory(self, "选择图片文件夹")
                if folder_path:  # 确保用户选择了文件夹
                    file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif'))]
            
            # 如果用户选择了文件且文件列表不为空，则启动转换线程
            if file_paths:
                self.getProgressDialog("转换进度")
                thread = ConvertThread(file_paths)
                thread.progress_changed.connect(self.update_progress)
                thread.finished.connect(self.convert_finished)
                thread.error_occurred.connect(self.handle_error)
                thread.start()
                
                self.progress_dialog.exec()
            elif folder_path and not file_paths:
                # 如果选择了文件夹但没有找到图片文件
                self.getMessageBox("所选文件夹中没有找到图片文件")
        except Exception as e:
            print(f"选择文件过程中出错: {e}")
            self.getMessageBox(f"操作失败: {str(e)}")

    def convert_finished(self):
        # 关闭进度对话框
        self.progress_dialog.close()
        self.getMessageBox("图片转换完成")

    # 选择PDF并转换为图片
    def convert_pdf_to_images(self):
        file_paths = []
        folder_path = None  # 初始化folder_path变量
        try:
            if self.convert_file_method == "file":
                file_paths, _ = QFileDialog.getOpenFileNames(self, "选择PDF文件", "", "PDF Files (*.pdf)")
            elif self.convert_file_method == "folder":
                folder_path = QFileDialog.getExistingDirectory(self, "选择PDF文件夹")
                if folder_path:  # 确保用户选择了文件夹
                    file_paths = [os.path.join(folder_path, file) for file in os.listdir(folder_path) if file.endswith('.pdf')]
            
            # 如果用户选择了文件且文件列表不为空，则启动转换线程
            if file_paths:
                self.getProgressDialog("转换进度")
                thread = PdfToImageThread(file_paths)
                thread.progress_changed.connect(self.update_progress)
                thread.finished.connect(self.convert_finished)
                thread.error_occurred.connect(self.handle_error)
                thread.start()
                
                self.progress_dialog.exec()
            elif folder_path and not file_paths:
                # 如果选择了文件夹但没有找到PDF文件
                self.getMessageBox("所选文件夹中没有找到PDF文件")
        except Exception as e:
            print(f"选择文件过程中出错: {e}")
            self.getMessageBox(f"操作失败: {str(e)}")

    def show_version_dialog(self):
        dialog = VersionDialog()
        dialog.exec()
        
    def show_intro_dialog(self):
        dialog = IntroDialog()
        dialog.exec()

if __name__ == "__main__":
    # 创建Qt应用程序
    app = QApplication(sys.argv)

    # 创建主窗口
    main_window = MainWindow()
    main_window.show()

    # 运行应用程序
    sys.exit(app.exec())

